package org.tp;

import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.ArrayList;

import javax.swing.JOptionPane;
/** Classe gerant les fonctions de notre programme**/
public class Fonctions {

	String forme;
	String type;
	String id;
	String lparam;
	String linstr;
	List<String[]> liste_decla;
	Set<String> liste_var;
	Map<Set<String>, String> liste_instr;

	Map<String, Fonctions> mapFunc;

	public Fonctions() {
		// TODO Auto-generated constructor stub
		mapFunc = new HashMap<String, Fonctions>();

	}

	public Fonctions(String forme, String type, String id, String lparam,
			String linstr, List<String[]> liste_decla, Set<String> liste_var, Map<Set<String>, String> liste_instr) {
		// TODO Auto-generated constructor stub
		this.forme = forme;
		this.type = type;
		this.id = id;
		this.lparam = lparam;
		this.linstr = linstr;
		this.liste_decla = liste_decla;
		this.liste_var = liste_var;
		this.liste_instr = liste_instr;
	}
	/** Ajouter une fonction dans notre Map**/
	public void addMapFunc(String forme, String type, String id, String lparam,
			String linstr, List<String[]> liste_decla, Set<String> liste_var, Map<Set<String>, String> liste_instr) {
		mapFunc.put(type + " " + id + " " + lparam, new Fonctions(forme, type,
				id, lparam, linstr, liste_decla, liste_var,liste_instr));
	}
	/** retourner la fonction presente dans la Map suivant un id**/
	public Fonctions getFunc(String id) {
		return mapFunc.get(id);
	}
	/** Accesseur concernant la map des fonctions**/
	public Map<String, Fonctions> getMapFunc() {
		return mapFunc;
	}
	/** Accesseur concernant la map des fonctions predefinies**/
	public Map<String, String> getMapPred() {
		return Main.mapPred;
	}

	/** Affichage d'une fonction**/
	public String afficherFunc() {
		if (this.forme.equals("procedure"))
		{
			return new String("\tpublic void " + id + "(" + lparam + ") {\n"
					+ linstr + "\n\t}\n\n");
		}
		else if (this.forme.equals("procedure class"))
			return new String("\t\tpublic void " + id + "(" + lparam + ") {\n\t"+ linstr + "\n\t\t}\n\n");
		else if (this.forme.equals("operator"))
			return new String("\tpublic" + id + " {\n" + linstr + "\n\t}\n\n");
		else if (this.forme.equals("operator class"))
			return new String("\t\tpublic" + id + " {\n\t" + linstr + "\n\t\t}\n\n");
		else if (this.forme.equals("constructor"))
			return new String(id + " {\n" + linstr + "\n\t}\n\n");
		else if (this.forme.equals("constructor class"))
			return new String("\t"+id + " {\n\t" + linstr + "\n\t\t}\n\n");
		else if(this.id.equals("main"))
			return new String("\tpublic"+ " " + "static " + type + " " + id + "(" + lparam+ ") {\n" + linstr + "\n\t}\n\n");
		else
			return new String("\tpublic " + type + " " + id + "(" + lparam+ ") {\n" + linstr + "\n\t}\n\n");
	}
	/** Affichage de toute les fonctions presentes dans la map**/
	public String afficherAllFunc() {
		String s1 = "";
		String tmp = "";// les constructeurs
		String tmp2 = "";// operators
		String tmp3 = "";// les autres fonctions
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet())
			if(!entry.getValue().forme.contains("class")) {
				if (entry.getValue().forme.equals("constructor"))
					tmp = tmp.concat(entry.getValue().afficherFunc());
				else if (entry.getValue().forme.equals("operator"))
					tmp2 = tmp2.concat(entry.getValue().afficherFunc());
				else
					tmp3 = tmp3.concat(entry.getValue().afficherFunc());
			}
		s1 = s1.concat(tmp).concat(tmp2).concat(tmp3);
		return s1;
	}
	/** Recherche du main pour voir si une fonction main est bien declaree**/
	public void rechercheMain() {
		boolean tmp = false;
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			if (entry.getKey().contains("main")) {
				//System.out.println("MONTEST " + entry.getKey());
				tmp = true;
			}
		}
		if (tmp == false) {
			System.err.println("Main Inexistant");
			JOptionPane.showMessageDialog(null,
					"erreur : la fonction main n'existe pas");
		}
	}
	/** Affichage de toutes les declarations d'une fonction**/
	public void displayAllDecla() {
		System.out.println("ALL DECLA");
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			System.out.println("---------- ALL DECLA2 --------------");
			Fonctions tmp = entry.getValue();
			if (tmp.liste_decla.isEmpty())
				System.out.println("ALL DECLA42");

			for (String[] st : tmp.liste_decla) {
				System.out.println("ALL *DECLA3");
				System.out.println("ALL DECLA4: " + st[0] + " " + st[1]);
			}
		}
	}
	/** Affichage de toutes les variables des instructions d'une fonction**/
	public void displayAllInstr() {
		System.out.println("ALL INSTR");
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			System.out.println("------------ ALL INSTR2 ------------");
			Fonctions tmp = entry.getValue();
			if (tmp.liste_decla.isEmpty())
				System.out.println("ALL INSTR42");

			for (String st : tmp.liste_var) {
				System.out.println("ALL INSTR3");
				System.out.println("ALL INSTR4: " + st);
			}
		}
	}
	/** Affichage des structures presentes dans notre programme**/
	public void displayAllStruct() {
		for (Entry<String, List<String[]>> entry : Main.struct.entrySet()) {
			String structName = entry.getKey();
			System.out.println("---------------------------------");
			System.out.println("Nom de la structure: " + structName);
			List<String[]> champs = entry.getValue();
			for (String[] st : champs) {
				System.out.println("Champ: " + st[0] + " " + st[1] + " " + st[2]);
			}
			System.out.println();
		}
	}
	/** Verificateur si un type autre que les primitifs(int, boolean....) existe**/
	public boolean existType(String id) {
		for (Entry<String, List<String[]>> entry : Main.struct.entrySet()) {
			String structName = entry.getKey();
			if(id.equals(structName))
				return true;
		}
		for (Entry<String, Fonctions> entry2 : getMapFunc().entrySet()) {
			Fonctions tmp = entry2.getValue();
			if((tmp.forme.equals("constructor") || tmp.forme.equals("constructor class")) && tmp.id.contains(id))
				return true;
		}
		JOptionPane.showMessageDialog(null, "Attention le type "+id+" n'existe pas !");
		return false;
	}

	public void checkVarsWellDeclared() {
		checkVarsWellDeclaredInFunc();
	}
	/** booleen qui dit si il y a un doublon **/
	public boolean isDoublon(String s, Fonctions tmp) {
		for(String st : tmp.liste_var){
			if(st.contains(".")){
				String [] tmpsplit = st.split("\\.");
				if(tmpsplit[1].equals(s))
					return true;
			}
		}
		return false;
	}

	/** Retourne le type d'une declaration**/
	public String TypeDecla(String decla, Fonctions f) {
		for (Entry<String, Declarations> entry2 : Main.d.getMapDecla().entrySet()) {
			Declarations tmp2 = entry2.getValue();
			if(decla.equals(tmp2)) {
				return tmp2.type;
			}
		}
		for (String[] st : f.liste_decla) {
			if(decla.equals(st[0])) {
				return st[1];
			}
		}
		return "";
	}
	/** verification qu'une variable est declaree (dans une structure, dans une fonction, en locale, en globale)**/
	public void checkVarsWellDeclaredInFunc() {
		boolean exists = false;
		boolean doublon = true;
		boolean err = false;
		String typedecla = ""; 

		//System.out.println("---------------------------------");
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			Fonctions tmp = entry.getValue();
			for (String st : tmp.liste_var) {
				//System.out.println("Fonction: "+tmp.id+" variable: "+st);
				if(!isDoublon(st,tmp) && !st.equals("false") && !st.equals("true")) {
					doublon = false;
					for (String[] st2 : tmp.liste_decla) {
						for (Entry<String, Declarations> entry2 : Main.d.getMapDecla().entrySet()) {
							Declarations tmp2 = entry2.getValue();
							if (st.contains(".")) {
								String[] tmpsplit = st.split("\\.");
								if (!tmpsplit[0].equals(st)) {
									if (tmp2.forme.equals("struct") || tmp2.forme.equals("class")) {
										if(tmpsplit[0].equals(tmp2.id)) {
											for (Entry<String, List<String[]>> entry3 : Main.struct.entrySet()) {
												String nom = entry3.getKey();
												if (nom.equals(tmp2.id)) {
													List<String[]> champs = entry3.getValue();
													for (String[] st3 : champs) {
														if (tmpsplit[1].equals(st3[0])) {
															exists = true;
															//break;
														}
													}
												}
											}
										}
										else {
											if(tmpsplit[0].contains("[")) {
												tmpsplit[0] = tmpsplit[0].substring(0, tmpsplit[0].indexOf("["));
											}
											typedecla = TypeDecla(tmpsplit[0],tmp);
											if(tmpsplit[0].equals("this")) {
												for (Entry<String, List<String[]>> entrystrcut : Main.struct.entrySet()) {
													String nom = entrystrcut.getKey();
													if(tmp.id.contains(nom))
														typedecla = nom;
												}
											}
											if(typedecla.contains(tmp2.id)) {
												for (Entry<String, List<String[]>> entry4 : Main.struct.entrySet()) {
													String nom = entry4.getKey();
													if (nom.equals(tmp2.id)) {
														List<String[]> champs = entry4.getValue();
														for (String[] st4 : champs) {
															if (tmpsplit[1].equals(st4[0])) {
																exists = true;
																//break;
															}
														}
													}
												}
											}
										}
									}
								}
							}
							else {
								if(st.contains("[")) {
									st = st.substring(0, st.indexOf("["));
								}								

								if (st.equals(st2[0]) || st.equals(tmp2.id) ) {
									exists = true;
									//break;
								}
							}
						}
					}
				}
				if (exists == false && doublon == false) {
					JOptionPane.showMessageDialog(null, "La variable " + st + " de la fonction "+ tmp.id + " n'est pas déclarée !");
					System.err.println("La variable " + st + " de la fonction "	+ tmp.id + " n'est pas déclarée !");
					err = true;
				}
				exists = false;
				doublon = true;
				typedecla = "";
			}
		}
		if(!err)
			System.out.println("\nToutes les variables sont bien déclarées !");
	}
	/** Affichage des instructions**/
	public void displayAllInstType() {
		System.out.println("ALL INST PER LIGNES");
		for (Entry<Set<String>, String> entry : Main.mapVarPerInst.entrySet()) {
			String instName = entry.getValue();
			System.out.println("---------------------------------");
			System.out.println("Nom de la variable: " + instName);
			Set<String> l = entry.getKey();


			Iterator<String> i = l.iterator();
			while(i.hasNext()) {
				String st = i.next();
				System.out.println("var utilise: " + st);
			}
		}
	}
	/** Insertion des fonctions predefinies**/
	public void Foncpred(/*String arg*/)
	{
		//Main.mapPred.put("random", "(int)(Math.random() * ("+arg+"+1);");
		Main.mapPred.put("random", "random");
		Main.mapPred.put("length", "length");

	}

	public boolean FindFctPred(String id)
	{
		for(Entry<String, String> entry : Main.mapPred.entrySet()) {
			String fct = entry.getKey();
			if(id.equals(fct))
				return true;
		}
		return false;
	}
	/** Verification de tous les types de chaque declarations, comprenant aussi les paramètres, d'une fonction**/
	public void checkDeclaType() {
		boolean goodType = false;
		//System.out.println("ALL VERIF");
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			//System.out.println("---------- VERIF DECLA --------------");
			Fonctions tmp = entry.getValue();
			for (String[] st : tmp.liste_decla) {
				//System.out.println("Verif fonction "+tmp.id+" "+st[0]+" "+st[1]);

				for (Entry<String, List<String[]>> entrystruct : Main.struct.entrySet()) {
					String structName = entrystruct.getKey();

					if(st[1].contains(structName)||st[1].contains("int")||st[1].contains("boolean")
							||st[1].contains("String")||st[1].contains("double")||st[1].contains("char")) {
						goodType = true;
					}					
				}

				if(!goodType) {
					System.out.println("Attention le type "+st[1]+" n'existe pas !");
					JOptionPane.showMessageDialog(null, "Attention le type "+st[1]+" n'existe pas !");
				}
				goodType = false;
			}
		}
	}
	/** Verification de tous les types de chaques champs de structure ou classe**/
	public void checkStructType() {
		boolean goodType = false;
		//System.out.println("---------- VERIF TYPE STRUCT AND CLASS --------------");
		for (Entry<String, List<String[]>> entrystruct : Main.struct.entrySet()) {
			String structName = entrystruct.getKey();
			//System.out.println("name struct"+structName);
			List<String[]> champs = entrystruct.getValue();
			for (String[] st : champs) {
				//System.out.println("Champ: " + st[0] + " " + st[1]);
				for (Entry<String, List<String[]>> entrystruct2 : Main.struct.entrySet()) {
					String structName2 = entrystruct2.getKey();
					if(st[1].contains(structName2)||st[1].contains("int")||st[1].contains("boolean")
							||st[1].contains("String")||st[1].contains("double")||st[1].contains("char")) {
						goodType = true;
					}
				}

				if(!goodType) {
					System.out.println("Attention le type "+st[1]+" n'existe pas !");
					JOptionPane.showMessageDialog(null, "Attention le type "+st[1]+" n'existe pas !");
				}
				goodType = false;
			}
		}
	}
	/** Retourne le type d'un variable utilisé dans une instruction de tel fonction**/
	public String TypeInst(String inst, Fonctions f) {
		for (Entry<String, List<String[]>> entrystruct : Main.struct.entrySet()) {
			List<String[]> champs = entrystruct.getValue();
			for (String[] st : champs) {
				if(st[0].equals(inst))
					return st[1];
			}
		}

		for (Entry<String, Fonctions> entryfunc : getMapFunc().entrySet()) {
			Fonctions tmp = entryfunc.getValue();
			if(tmp.equals(f)) {
				for (String[] str : tmp.liste_decla) {
					if(str[0].equals(inst))
						return str[1];
				}
			}
		}

		return "";
	}
	/** Verification de tous les types de chaque instructions	**/
	public void checkInstType() {
		//System.out.println("------------------------------------");
		boolean ok = true;
		String typeInstName = "";
		List<String> tabtype = new ArrayList<String>();

		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			Fonctions tmp = entry.getValue();
			//System.out.print("\nFonction: "+tmp.id);
			for(Entry<Set<String>, String> entry2 : tmp.liste_instr.entrySet()) {
				String instName = entry2.getValue();
				if(!(instName.equals("for") || instName.equals("repeat") || instName.equals("if")
						|| instName.equals("ifelse") || instName.equals("return") || instName.equals("{")
						|| instName.equals("while") || instName.equals("case"))) {
					//System.out.print("-"+instName+" : ");
					String toto = instName;
					//System.out.println("TOTO PLOP "+instName);
					if(instName.contains("[")) {
						if(instName.contains("\\."))
							toto = instName.substring(instName.indexOf(".")+1, instName.length());
						else 
							toto = instName.substring(0,instName.indexOf("["));
					}
					else if(instName.contains("this."))
						toto = instName.replace("this.", "");
					if(instName.contains(".")) {
						//System.out.println("TOTO PLOP 2");
						//System.out.println("TOTO : "+toto);
						toto = instName.substring(instName.indexOf(".")+1, instName.length());
						//System.out.println("TOTO 2: "+toto);
						//System.out.println("TOTO : "+toto);
						/*while(toto.contains(".")){
							toto = instName.substring(instName.indexOf(".")+1, instName.length());
							//System.out.println("TOTO2 : "+toto);
						}*/
						//System.out.println("TOTO : "+toto);
					}

					typeInstName = TypeInst(toto,tmp);
					//System.out.println("TOTO TYPE: "+typeInstName);
					if(!typeInstName.equals(""))
						typeInstName = typeInstName.replace("\t","");
					//System.out.println("TOTO TYPE 2: "+typeInstName);
					tabtype.add(typeInstName);


					Set<String> l0 = entry2.getKey();				
					Iterator<String> z = l0.iterator();
					List<String> tabmem = new ArrayList<String>();
					String mem;
					while(z.hasNext()) {
						String stri = z.next();
						if(stri.contains("[")) {
							mem = stri.substring(stri.indexOf("[")+1, stri.indexOf("]"));
							tabmem.add(mem);
							//System.out.print("mem"+mem);
						}
					}

					Set<String> l = entry2.getKey();				
					Iterator<String> i = l.iterator();
					while(i.hasNext()) {
						String st = i.next();

						if(!tabmem.isEmpty()) { 

							for(String tb : tabmem) {

								if(!st.equals(instName) && !instName.contains(st) && !st.contains(tb)){
									//System.out.print("\t"+st);
									if(st.contains("[")){
										if(st.contains("\\."))
											st = st.substring(st.indexOf(".")+1, st.length());
										else 
											st = st.substring(0,st.indexOf("["));
									}
									else if(st.contains(".")) {
										//System.out.print("TYPE BLABLA"+st);
										st = st.substring(st.indexOf(".")+1, st.length());
										//System.out.print("TYPE BLABLA 2"+st);

									}

									String typeVarUsed = TypeInst(st,tmp);
									if(!typeVarUsed.equals(""))
										typeVarUsed = typeVarUsed.replace("\t","");

									if(st.equals("true")||st.equals("false")){
										typeVarUsed = "boolean";
									}
									tabtype.add(typeVarUsed);
								}
							}
						}
						else {
							if(!st.equals(instName) && !instName.contains(st)){
								//System.out.print("\t"+st);
								if(st.contains("[")){
									if(st.contains("\\."))
										st = st.substring(st.indexOf(".")+1, st.length());
									else 
										st = st.substring(0,st.indexOf("["));
								}
								else if(st.contains(".")) {
									//System.out.print("TYPE BLABLA"+st);
									st = st.substring(st.indexOf(".")+1, st.length());
									//System.out.print("TYPE BLABLA 2"+st);

								}

								String typeVarUsed = TypeInst(st,tmp);
								if(!typeVarUsed.equals(""))
									typeVarUsed = typeVarUsed.replace("\t","");

								if(st.equals("true")||st.equals("false")){
									typeVarUsed = "boolean";
								}
								tabtype.add(typeVarUsed);
							}
						}
						//tabmem.clear();
					}
					/*System.out.println();
					System.out.print("types:");
					for(String str : tabtype) {
						System.out.print("\t"+str);
					}*/
					for(int m=0; m < tabtype.size(); m++) {
						for(int n=1; n < tabtype.size(); n++) {
							if(!(tabtype.get(m).replace("[]", "")).contains((tabtype.get(n).replace("[]", "")))) {
								ok = false;
								System.out.println("erreur: L'instruction contenant les variables de type "
										+tabtype.get(m)+" "+tabtype.get(n)+" de la fonction "
										+tmp.id+" n'ont pas le même type");
								JOptionPane.showMessageDialog(null, "L'instruction contenant les variables de type "
										+tabtype.get(m)+" et "+tabtype.get(n)+" de la fonction "
										+tmp.id+" n'ont pas le même type");
								break;
							}
						}
					}
					//System.out.println("\n");
					tabtype.clear();
				}
			}
		}
		System.out.println("Les instructions de toutes les fonctions sont bien typées !");
	}
	/** Affichage de toutes les variables de chaques instructions lignes par lignes, pour chaques fonctions.
	 * Si l'instruction est une affectation, alors on a en premier élément le membre gauche, et ensuite
	 * la liste de ses membres droits.
	 * 
	 * Si ce n'est pas le cas, alors le membre gauche est remplacé par le type de l'instruction :
	 * 	c-à-d, "for", "if", "ifelse", "while", ...
	 * **/
	public void displayInstPerFunc() {
		System.out.println("---------- Display Test --------------");
		for (Entry<String, Fonctions> entry : getMapFunc().entrySet()) {
			Fonctions tmp = entry.getValue();
			System.out.println("** Fonction ** "+tmp.id);
			for(Entry<Set<String>, String> entry2 : tmp.liste_instr.entrySet()) {
				String instName = entry2.getValue();
				System.out.println("Nom de la variable: " + instName);
				Set<String> l = entry2.getKey();

				Iterator<String> i = l.iterator();
				while(i.hasNext()) {
					String st = i.next();
					System.out.println("var utilise: " + st);
				}
			}
		}
	}
	/**
	 * Fonction nous permettant d'afficher au choix (en commentant l'un ou l'autre) 
	 * des informations nécessaires et dont nous nous sommes servis le plus.
	 * Par exemple, toutes les déclarations d'une fonction, ou encore toutes ces 
	 * variables utilisées, ou bien les types de celles-ci ...
	 * */
	public void displayDebug() {
		/*Main.func.displayAllDecla();
		Main.func.displayAllInstr();
		Main.func.displayAllStruct();
		Main.func.displayAllInstType();
		Main.d.AffichageDeclaClass();
		Main.func.displayInstPerFunc();*/
	}
	/**
	 * Fonctions permettant de vérifier toutes les conditions nécessaires 
	 * pour avoir un fichier java correct.
	 * C-à-d, des types existant, des déclarations bien déclarés, 
	 * tous les prototypes implémentés, une fonction main existante ... 
	 * */
	public void checkAllConditions() {
		Main.func.rechercheMain();
		Main.p.controle_fct();
		Main.func.checkVarsWellDeclared();
		Main.func.checkDeclaType();
		Main.func.checkStructType();
		Main.func.checkInstType();
	}
}
